﻿using System;
using System.Linq;
using System.Text;
using KaroInterface;

namespace Emerald
{
    public class GameState
    {
        #region Delegates
        public delegate void PlayerChangedEvent(object sender, EColor newPlayer);
        public delegate void GameEndedEvent(object sender, EColor winningPlayer);
        public delegate void BoardChangedEvent(object sender, BoardPoint removed, BoardPoint added);
        #endregion

        #region Members

        private static Random random = new Random();
        private Action bestMove;
        private Action creationMove;
        private int evalValue;
        private int depth;
        private Board board;
        private EColor colorToPlay;
        private ulong hashCode;
        private GameState[] nextPossibleStatesArray = new GameState[200];

        #endregion

        #region Properties

        public ulong HashCode
        {
            get { return hashCode; }
        }

        public Board Board
        {
            get { return board; }
        }
        public EColor ColorToPlay
        {
            get { return colorToPlay; }
        }

        public int Depth
        {
            get { return depth; }
            set { depth = value; }
        }

        public IAction CreationMove
        {
            get { return creationMove; }
        }

        public int EvalValue
        {
            get { return evalValue; }
            set { evalValue = value; }
        }

        public GameState[] NextPossibleStatesArray
        {
            set { nextPossibleStatesArray = value; }
        }

        public string DataString
        {
            get
            {
                return CalculateDataString();
            }
        }

        #endregion

        #region Events
        internal event PlayerChangedEvent PlayerChanged;
        internal event GameEndedEvent GameEnded;
        internal event BoardChangedEvent BoardChanged
        {
            add { board.BoardChanged += value; }
            remove { board.BoardChanged -= value; }
        }
        #endregion

        #region Constructors

        public GameState(EColor colorToPlay)
        {
            this.colorToPlay = colorToPlay;
            this.board = new Board();
            this.hashCode = TranspositionTable.Instance.CalculateHashCode(this);
        }

        public GameState(EColor colorToPlay, int depth)
        {
            this.colorToPlay = colorToPlay;
            board = new Board();
            this.depth = depth;
            hashCode = TranspositionTable.Instance.CalculateHashCode(this);
        }


        public GameState(GameState gs)
        {
            this.colorToPlay = gs.ColorToPlay;
            this.board = new Board(gs.Board);
            this.hashCode = gs.HashCode;
            this.depth = gs.Depth;
            this.evalValue = gs.evalValue;
            this.bestMove = gs.bestMove;
        }

        public GameState(string gameStateString)
        {
            if (gameStateString == null)
                return;

            // first char: Color to play: 1 or 0
            colorToPlay = gameStateString[0] == '0' ? EColor.White : EColor.Red;
            string boardString = gameStateString.Substring(1, gameStateString.Length - 1);
            board = new Board(boardString);
            this.hashCode = TranspositionTable.Instance.CalculateHashCode(this);
        }

        #endregion

        #region Methods
        public bool PerformActionWithoutRulesAndWinCheck(Action a)
        {
            creationMove = a;
            bool performed = board.PerformActionWithoutRules(a);
            if (performed)
            {
                changePlayer();
            }
            return performed;
        }

        public bool PerformActionWithoutRules(Action a)
        {
            creationMove = a;
            bool performed = board.PerformActionWithoutRules(a);
            if (performed)
            {
                checkGameEnded();
                changePlayer();
            }
            return performed;
        }

        public bool PerformActionWithoutWinCheck(Action action)
        {
            return PerformDefinableAction(action, false);
        }

        public bool PerformAction(Action action)
        {
            return PerformDefinableAction(action, true);
        }

        private bool PerformDefinableAction(Action action, bool winCheck)
        {
            if (board.PerformAction(action))
            {
                creationMove = action;

                if (winCheck)
                    checkGameEnded();

                changePlayer();

                return true;
            }
            return false;
        }

        private void changePlayer()
        {
            colorToPlay = colorToPlay.Opposite();
            if (PlayerChanged != null)
                PlayerChanged(this, colorToPlay);
        }

        private void checkGameEnded()
        {
            EColor? winningColor = IsWonByPlayer();

            if (winningColor == null) return;

            if (GameEnded != null)
                GameEnded(this, winningColor.Value);
        }

        public GameState[] NextPossibleStates(int depth)
        {
            //if (nextPossibleStatesArray[0] == null)
            GameState[] nextPossibleStatesArray = new GameState[200];
            {

                int index = 0;
                //Stopwatch sw = Stopwatch.StartNew();
                //determine CurrentPhase
                bool movePhase = false;
                if (board.BoardArray.Pieces.Count(p => p != null) == 12)
                    movePhase = true;

                //if movePhase determine where a piece has to be set
                if (!movePhase)
                {
                    Tile[] tiles = Board.BoardArray.Tiles;
                    for (int i = 0; i < tiles.Length; i++)
                    {
                        if (tiles[i] != null && tiles[i].Piece == null)
                        {
                            //clone
                            GameState returnState = this.Clone();
                            //create and perform action
                            Action a = new Action(tiles[i].Location.X, tiles[i].Location.Y, returnState.colorToPlay);
                            if (returnState.PerformActionWithoutRulesAndWinCheck(a))
                            {
                                //return
                                nextPossibleStatesArray[index] = returnState;
                                index++;
                            }
                        }
                    }
                }
                else
                {
                    Tile[] tiles = Board.BoardArray.Tiles;
                    for (int k = 0; k < tiles.Length; k++)
                    {
                        if (tiles[k] != null && tiles[k].Piece != null && tiles[k].Piece.Color == colorToPlay)
                        {
                            //move 1 tile in each direction
                            for (int i = -1; i < 2; i++)
                            {
                                for (int j = -1; j < 2; j++)
                                {
                                    //if not 0,0
                                    if (false == (i == 0 && j == 0))
                                    {
                                        //if there is a tile present
                                        if (board.BoardArray[(byte)(tiles[k].Location.X + i), (byte)(tiles[k].Location.Y + j)] != null)
                                        {
                                            //and there is no piece on it
                                            if (board.BoardArray[(byte)(tiles[k].Location.X + i), (byte)(tiles[k].Location.Y + j)].Piece == null)
                                            {
                                                //clone state
                                                GameState returnState = this.Clone();
                                                //create and perform action
                                                Action a = new Action(tiles[k].Location.X, tiles[k].Location.Y, (byte)(tiles[k].Location.X + i), (byte)(tiles[k].Location.Y + j));
                                                if (returnState.PerformActionWithoutRulesAndWinCheck(a))
                                                {
                                                    //return
                                                    nextPossibleStatesArray[index] = returnState;
                                                    index++;
                                                }
                                            }
                                            //if there is no next position one further
                                            else if (board.BoardArray[(byte)(tiles[k].Location.X + i + i), (byte)(tiles[k].Location.Y + j + j)] == null)
                                            {
                                                Tile[] mt = board.BoardArray.MovableTiles;
                                                int count = mt.Count(t => t != null);
                                                for (int l = 0; l < mt.Length; l++)
                                                {
                                                    if (mt[l] != null)
                                                    {
                                                        if (depth != 0)
                                                            l = GameState.random.Next(count);

                                                        //clone state
                                                        GameState returnState = this.Clone();
                                                        //create and perform action
                                                        Action a = new Action(mt[l].Location.X, mt[l].Location.Y, tiles[k].Location.X, tiles[k].Location.Y, (byte)(tiles[k].Location.X + i + i), (byte)(tiles[k].Location.Y + j + j));
                                                        if (a.OldPiecePosition == a.OldTilePosition)
                                                            continue;
                                                        if (returnState.PerformActionWithoutWinCheck(a))
                                                        {
                                                            //return
                                                            nextPossibleStatesArray[index] = returnState;
                                                            index++;
                                                        }

                                                        if (depth != 0)
                                                            break;

                                                    }
                                                }
                                            }
                                            //check if the next position one further is free
                                            else if (board.BoardArray[(byte)(tiles[k].Location.X + i + i), (byte)(tiles[k].Location.Y + j + j)].Piece == null)
                                            {
                                                //clone state
                                                GameState returnState = this.Clone();
                                                //create and perform action
                                                Action a = new Action(tiles[k].Location.X, tiles[k].Location.Y, (byte)(tiles[k].Location.X + i + i), (byte)(tiles[k].Location.Y + j + j));
                                                if (returnState.PerformActionWithoutRulesAndWinCheck(a))
                                                {
                                                    //return
                                                    nextPossibleStatesArray[index] = returnState;
                                                    index++;
                                                }
                                            }
                                        }
                                        //check if you can move a tile in order to go to this position
                                        else
                                        {
                                            Tile[] mt = board.BoardArray.MovableTiles;
                                            int count = mt.Count(t => t != null);
                                            for (int l = 0; l < mt.Length; l++)
                                            {
                                                if (mt[l] != null)
                                                {
                                                    if (depth != 0)
                                                        l = GameState.random.Next(count);

                                                    if (mt[l].Location == tiles[k].Location)
                                                        continue;
                                                    //clone state
                                                    GameState returnState = this.Clone();
                                                    //create and perform action
                                                    Action a = new Action(mt[l].Location.X, mt[l].Location.Y, tiles[k].Location.X, tiles[k].Location.Y, (byte)(tiles[k].Location.X + i), (byte)(tiles[k].Location.Y + j));
                                                    if (returnState.PerformActionWithoutWinCheck(a))
                                                    {
                                                        //return
                                                        nextPossibleStatesArray[index] = returnState;
                                                        index++;
                                                    }

                                                    if (depth != 0)
                                                        break;

                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return nextPossibleStatesArray;
        }

        public EColor? IsWonByPlayer()
        {
            return board.IsWonByPlayer();
        }

        public GameState Clone()
        {
            return new GameState(this);
        }

        public int CountPieces(string color)
        {
            return board.BoardArray.CountPieces((EColor)Enum.Parse(typeof(EColor), color));
        }

        private string CalculateDataString()
        {
            StringBuilder strBuilder = new StringBuilder();

            if (colorToPlay == EColor.White)
            {
                strBuilder.Append("0");
            }
            else
            {
                strBuilder.Append("1");
            }

            for (byte i = 0; i < 21; i++)
            {
                for (byte j = 0; j < 20; j++)
                {
                    Tile temp = Board.BoardArray[i, j];
                    string str = "";

                    if (temp == null)
                    {
                        // no tile
                        str = " ";
                    }
                    else if (temp.Piece == null)
                    {
                        // no piece but tile
                        str = ".";
                    }
                    else
                    {
                        // marked piece: W or R
                        if (temp.Piece.Color == EColor.Red)
                            str = "R";
                        else if (temp.Piece.Color == EColor.White)
                            str = "W";

                        if (temp.Piece.State == EState.Unmarked)
                        {
                            // unmarked piece: w or r
                            str = str.ToLower();
                        }
                    }

                    strBuilder.Append(str);
                }
            }

            return strBuilder.ToString();
        }

        #endregion

    }
}
